/*
 * The contents of this web application are subject to the Mozilla Public License Version 
 * 1.1 (the "License"); you may not use this web application except in compliance with 
 * the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, 
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
 * for the specific language governing rights and limitations under the License.
 * 
 * The Original Code is owned by and the Initial Developer of the Original Code is 
 * Composite A/S (Danish business reg.no. 21744409). All Rights Reserved
 * 
 * Section 11 of the License is EXPRESSLY amended to include a provision stating 
 * that any dispute, including but not limited to disputes related to the enforcement 
 * of the License, to which Composite A/S as owner of the Original Code, as Initial 
 * Developer or in any other role, becomes a part to shall be governed by Danish law 
 * and be initiated before the Copenhagen City Court ("K�benhavns Byret")            
 */

using System;
using System.Collections.Generic;
using System.Web;
using System.Runtime.Remoting.Messaging;


namespace Composite.Core.Caching
{
    // See http://piers7.blogspot.dk/2005/11/threadstatic-callcontext-and_02.html for details on HttpContext.Items vs. CallContext vs. ThreadStatic
    /// <summary>
    /// Cache for storing objects with a longevity limited to a single request. 
    /// Objects cached here are subject to garbage collection at some point after the request has completed.
    /// Uses HttpContext.Items when available, otherwise <see cref="System.Runtime.Remoting.Messaging.CallContext" />.
    /// </summary>
    public static class RequestLifetimeCache
    {
        /// <summary>
        /// Add an item to the cache.
        /// </summary>
        /// <param name="key">Key for item. Used when retrieving/clearing item later.</param>
        /// <param name="value">The item to store.</param>
        public static void Add(object key, object value)
        {
            Verify.ArgumentNotNull(key, "key");

            var httpContext = HttpContext.Current;

            if (httpContext != null)
            {
                httpContext.Items.Add(key, value);
            }
            else
            {
                FallbackCache.Add(key, value);
            }
        }



        /// <summary>
        /// Checks if the cache has the provided key.
        /// </summary>
        /// <param name="key">Key for item</param>
        /// <returns>True when item exist in cache. Otherwise false.</returns>
        public static bool HasKey(object key)
        {
            Verify.ArgumentNotNull(key, "key");

            var httpContext = HttpContext.Current;

            if (httpContext != null)
            {
                return httpContext.Items.Contains(key);
            }

            return FallbackCache.ContainsKey(key);
        }



        /// <summary>
        /// Returns cached item based on the provided key or null if item is not known.
        /// </summary>
        /// <param name="key">Key for item</param>
        /// <returns>Cached item or null if not found.</returns>
        public static object TryGet(object key)
        {
            Verify.ArgumentNotNull(key, "key");

            var context = HttpContext.Current;

            if (context != null)
            {
                return context.Items[key];
            }

            return (FallbackCache.ContainsKey(key) ? FallbackCache[key] : null);
        }



        /// <summary>
        /// Returns cached item based on the provided key or null if item is not known.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">Key for item</param>
        /// <returns>Cached item or null if not found.</returns>
        public static T TryGet<T>(object key)
        {
            Verify.ArgumentNotNull(key, "key");

            object result = TryGet(key);

            if (result != null)
            {
                return (T)result;
            }

            return default(T);
        }



        /// <summary>
        /// Returns cached item based on the provided key or a new instance which gets added to the cache using the key. 
        /// The returned item is guaranteed to exist in the cache.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">Key for item</param>
        /// <returns>Cached item or a new instance if not found.</returns>
        internal static T GetCachedOrNew<T>(object key) where T : new()
        {
            Verify.ArgumentNotNull(key, "key");

            var httpContext = HttpContext.Current;

            if (httpContext != null) {

                if (!httpContext.Items.Contains(key))
                {
                    lock (httpContext.Items)
                    {
                        if (!httpContext.Items.Contains(key))
                        {
                            T result = new T();

                            httpContext.Items.Add(key, result);
                            return result;
                        }
                    }
                }

                return (T)httpContext.Items[key];
            } 


            // FallbackCache is thread specific so no need to have a critical section
            var fallbackCache = FallbackCache;
            if (!fallbackCache.ContainsKey(key))
            {
                T result = new T();
                fallbackCache.Add(key, result);

                return result;
            }

            return (T)fallbackCache[key];
        }


        /// <summary>
        /// Remove a named item from the cache.
        /// </summary>
        /// <param name="key">Key for item to remove</param>
        public static void Remove(object key)
        {
            Verify.ArgumentNotNull(key, "key");

            var context = HttpContext.Current;

            if (context != null)
            {
                context.Items.Remove(key);
            }
            else
            {
                FallbackCache.Remove(key);
            }
        }


        private static Dictionary<object, object> FallbackCache
        {
            get
            {
                Dictionary<object, object> fallback = CallContext.GetData("RequestLifetimeCache:Fallback") as Dictionary<object, object>;

                if (fallback==null)
                {
                    fallback = new Dictionary<object, object>();
                    CallContext.SetData("RequestLifetimeCache:Fallback", fallback);
                }

                return fallback;
            }
        }

        /// <exclude />
        [Obsolete("Should no longer be used", true)]
        public static void ClearAll()
        {
        }
    }
}
